package com.sh.service.store.impl;

import cn.hutool.core.date.DateUtil;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.sh.common.exception.BizException;
import com.sh.config.OssClientConfig;
import com.sh.entity.Bucket;
import com.sh.entity.ObjectItem;
import com.sh.enums.OssTypeEnum;
import com.sh.enums.PermissionTypeEnum;
import com.sh.pojo.bo.CopyObjectsBO;
import com.sh.pojo.dto.ResourceTreeDTO;
import com.sh.pojo.vo.DownLoadObjectVO;
import com.sh.service.store.IOSService;
import com.sh.utils.CommonUtils;
import com.sh.utils.PathUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
public class AliOSSService implements IOSService {

    private OssClientConfig ossClientConfig;
    private Map<String, OSS> ossClients;
    private OSS initOssClient;

    public AliOSSService(OssClientConfig ossClientConfig) {
        this.ossClientConfig = ossClientConfig;
        log.info("===================");
        if (OssTypeEnum.OSS.getName().equals(ossClientConfig.getResourceType())) {
            initOssClient = new OSSClientBuilder().build(ossClientConfig.getEndpoint(),
                    ossClientConfig.getAccessKey(),
                    ossClientConfig.getSecretKey());

        }
        changeRegionClient();
    }

    private void changeRegionClient() {
        ossClients = new HashMap<>();
        List<com.aliyun.oss.model.Bucket> bs = initOssClient.listBuckets();
        for (com.aliyun.oss.model.Bucket b : bs) {
            OSS ossClient = new OSSClientBuilder().build(b.getExtranetEndpoint(),
                    ossClientConfig.getAccessKey(),
                    ossClientConfig.getSecretKey());
            ossClients.put(b.getName(), ossClient);

        }
    }

    @Override
    public List<Bucket> listBuckets() {
        List<Bucket> buckets = new ArrayList<>();
        try {
            List<com.aliyun.oss.model.Bucket> bs = initOssClient.listBuckets();
            bs.forEach(b -> {
                Bucket bucket = new Bucket();
                bucket.setLocation(b.getLocation());
                bucket.setName(b.getName());
                buckets.add(bucket);
            });
        } catch (Exception e) {
            log.error("list buckets error", e);
        }
        return buckets;
    }

    @Override
    public List<String> listAllObjects(String bucket, String objectPath) {
        List<String> objectKeys = new ArrayList<>();
        String nextMarker = null;
        ObjectListing objectListing;

        do {
            // 构造 ListObjectsRequest 对象，并设置前缀和标记（分页）
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucket)
                    .withPrefix(objectPath)  // 以指定的文件夹路径为前缀
                    .withMarker(nextMarker)
                    .withMaxKeys(1000);        // 每次最多列出 1000 个对象
            OSS ossClient = ossClients.get(bucket);

            objectListing = ossClient.listObjects(listObjectsRequest);
            List<OSSObjectSummary> objectSummaries = objectListing.getObjectSummaries();
            for (OSSObjectSummary summary : objectSummaries) {
                objectKeys.add(summary.getKey());  // 添加对象键（即对象路径）
            }

            nextMarker = objectListing.getNextMarker();
        } while (objectListing.isTruncated());  // 如果结果被截断，继续列出后续部分

        return objectKeys;
    }

    @Override
    public List<ObjectItem> listObjects(String bucketName, String prefix) {
        return listObjects(bucketName, prefix, "");
    }

    @Override
    public List<ObjectItem> listObjects(String bucketName, String path, String keyword) {
        OSS ossClient = ossClients.get(bucketName);
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        if (StringUtils.isEmpty(keyword)) {
            keyword = "";
        }
        Pattern pattern = Pattern.compile(keyword, Pattern.CASE_INSENSITIVE);
        List<ObjectItem> objects = new ArrayList<>();
        try {
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
            listObjectsRequest.setMaxKeys(1000);
            listObjectsRequest.setBucketName(bucketName);
            listObjectsRequest.setPrefix(path);
            listObjectsRequest.setDelimiter("/");
            ObjectListing results;
            do {
                results = ossClient.listObjects(listObjectsRequest);
                results.getObjectSummaries().forEach(item -> {
                    if (pattern.matcher(item.getKey()).find()) {
                        ObjectItem objectItem = ObjectItem.builder()
                                .objectName(item.getKey())
                                .size(item.getSize())
                                .isDir(false)
                                .build();
                        objects.add(objectItem);
                    }
                });
                // 处理目录
                results.getCommonPrefixes().forEach(prefixItem -> {
                    if (pattern.matcher(prefixItem).find()) {
                        ObjectItem objectItem = ObjectItem.builder()
                                .objectName(prefixItem)
                                .size(0L) // 目录的大小通常设置为0
                                .isDir(true)
                                .build();
                        objects.add(objectItem);
                    }
                });

                if (results.isTruncated()) {
                    listObjectsRequest.setMarker(results.getNextMarker());
                }
            } while (results.isTruncated());
        } catch (Exception e) {
            log.error("list object error", e);
        }
        return objects;
    }

    @Override
    public InputStream getObject(String bucketName, String prefix) {
        OSS ossClient = ossClients.get(bucketName);

        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        return ossClient.getObject(bucketName, prefix).getObjectContent();
    }

    @Override
    public String signUrl(String bucketName, String objectName, Integer expires) {
        OSS ossClient = ossClients.get(bucketName);

        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        if (expires == null) {
            expires = ossClientConfig.getSignExpires();
        }
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET);
        request.setExpiration(DateUtil.offsetDay(new Date(), expires));
        URL url = ossClient.generatePresignedUrl(request);
        return url.toString();

    }

    @Override
    public void putObject(String bucketName, String objectName, MultipartFile file) throws BizException {
        OSS ossClient = ossClients.get(bucketName);

        if (StringUtils.isEmpty(bucketName)) {
            bucketName = ossClientConfig.getBucket();
        }
        try {
            ossClient.putObject(bucketName, objectName, file.getInputStream());
        } catch (Exception e) {
            log.error("put object error", e);
            throw new BizException("上传对象失败：" + e.getMessage());
        }

    }

    @Override
    public List<String> getFirstFolder(String bucketName) {
        OSS ossClient = ossClients.get(bucketName);

        // 创建 ListObjectsRequest
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName)
                .withDelimiter("/") // 分隔符，表示按目录查询
                .withPrefix("");     // 查询根目录，可以指定其他路径

        // 列出对象
        ObjectListing listing = ossClient.listObjects(listObjectsRequest);

        // 输出 "下一级路径" (CommonPrefixes 表示“子目录”)
        return listing.getCommonPrefixes();
    }

    @Override
    public List<ResourceTreeDTO> listDirectories(String bucketName, String prefix, int currentDepth, int maxDepth) {
        OSS ossClient = ossClients.get(bucketName);

        List<ResourceTreeDTO> treeNodes = new ArrayList<>();
        // 如果是根目录（桶的起点），添加桶节点
        if (currentDepth == 0 && StringUtils.isEmpty(prefix)) {
            ResourceTreeDTO bucketNode = new ResourceTreeDTO();
            bucketNode.setName(bucketName);  // 桶名作为根节点名
            bucketNode.setLevel(currentDepth);  // 根节点的层级为0
            bucketNode.setPermission(PermissionTypeEnum.NONE.getCode());  // 桶的初始权限
            treeNodes.add(bucketNode);

            // 递归进入下一级目录（桶内的内容），所以要更新currentDepth
            List<ResourceTreeDTO> children = listDirectories(bucketName, prefix, currentDepth + 1, maxDepth);
            bucketNode.setChildren(children);

            return treeNodes;  // 返回包含桶节点的完整树
        }
        // 如果到达最大深度，则停止递归
        if (currentDepth >= maxDepth) {
            return treeNodes;
        }

        // 创建 ListObjectsRequest 对象
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName)
                .withDelimiter("/")    // 使用 '/' 作为分隔符，模拟目录结构
                .withPrefix(prefix);   // 设置前缀，指定从哪开始列出内容

        // 获取对象列表
        ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);

        // 列出目录 (CommonPrefixes)
        List<String> commonPrefixes = objectListing.getCommonPrefixes();

        for (String commonPrefix : commonPrefixes) {
            // 创建树节点
            ResourceTreeDTO treeNode = new ResourceTreeDTO();
            treeNode.setName(CommonUtils.getLastDirectoryName(commonPrefix));
            treeNode.setLevel(currentDepth);
            treeNode.setPermission(PermissionTypeEnum.NONE.getCode());
            treeNode.setPath(commonPrefix);

            // 递归调用，处理子目录
            List<ResourceTreeDTO> children = listDirectories(bucketName, commonPrefix, currentDepth + 1, maxDepth);
            treeNode.setChildren(children);

            // 添加到当前层级的树节点列表
            treeNodes.add(treeNode);
        }

        return treeNodes;
    }

    @Override
    public void createFolder(String bucket, String path) {
        OSS ossClient = ossClients.get(bucket);
        ossClient.putObject(bucket, path, new ByteArrayInputStream("".getBytes()));
    }

    @Override
    public void deleteObject(String bucket, String objectName) {
        OSS ossClient = ossClients.get(bucket);
        ossClient.deleteObject(bucket, objectName);
    }

    @Override
    public void deleteFolder(String bucket, String folderPath) {
        List<ObjectItem> objectItems = listObjects(bucket, folderPath);
        if (objectItems == null || objectItems.isEmpty()) {
            return;
        }
        List<String> objectKeys = new ArrayList<>();
        for (ObjectItem item : objectItems) {
            objectKeys.add(item.getObjectName());
            if (item.getIsDir()) {
                deleteFolder(bucket, item.getObjectName());
            }
        }
        deleteObjects(bucket, objectKeys);
    }

    @Override
    public void deleteObjects(String bucket, List<String> objectKeys) {
        List<String> deleteList = new ArrayList<>();
        // 使用 partitioningBy 根据是否以 '/' 结尾来分组
        Map<Boolean, List<String>> partitioned = objectKeys.stream()
                .collect(Collectors.partitioningBy(s -> s.endsWith("/")));

        deleteList.addAll(partitioned.get(false));
        List<String> folderList = partitioned.get(true);
        if (folderList != null && !folderList.isEmpty()) {
            for (String folder : folderList) {
                List<String> objects = listAllObjects(bucket, folder);
                deleteList.addAll(objects);
            }
        }
        OSS ossClient = ossClients.get(bucket);
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket)
                .withKeys(deleteList);
        ossClient.deleteObjects(deleteObjectsRequest);
    }

    @Override
    public void renameObject(String bucket, String oldFolder, String newFolder) {
        OSS ossClient = ossClients.get(bucket);

        // 将examplebucket下的srcobject.txt拷贝至同一Bucket下的destobject.txt。
        ossClient.copyObject(bucket, oldFolder, bucket, newFolder);

        // 删除srcobject.txt。
        ossClient.deleteObject(bucket, oldFolder);
    }

    @Override
    public void renameFolder(String bucket, String oldFolder, String newFolder) {
        OSS ossClient = ossClients.get(bucket);

        // Ensure the folder keys end with a slash
        oldFolder = oldFolder.endsWith("/") ? oldFolder : oldFolder + "/";
        newFolder = newFolder.endsWith("/") ? newFolder : newFolder + "/";

        // List all objects under the old folder
        ObjectListing objectListing = ossClient.listObjects(bucket, oldFolder);
        List<OSSObjectSummary> objectSummaries = objectListing.getObjectSummaries();

        // Copy each object to the new folder and delete the original
        for (OSSObjectSummary objectSummary : objectSummaries) {
            String oldObjectKey = objectSummary.getKey();
            String newObjectKey = oldObjectKey.replaceFirst(oldFolder, newFolder);

            // Copy the object
            ossClient.copyObject(bucket, oldObjectKey, bucket, newObjectKey);

            // Delete the original object
            ossClient.deleteObject(bucket, oldObjectKey);
        }
    }

    @Override
    public void copyObject(String bucket, String originalObjectKey, String newObjectKey) {
        OSS ossClient = ossClients.get(bucket);
        ossClient.copyObject(bucket, originalObjectKey, bucket, newObjectKey);
    }

    @Override
    public void copyObjects(CopyObjectsBO bo) {
        String bucket = bo.getBucket();
        OSS ossClient = ossClients.get(bucket);
        bo.getCopyList().forEach(e -> {
            String sourcePath = e.getSourcePath();
            String targetPath = e.getTargetPath();
            if (e.getSourcePath().endsWith("/")) {

                // List all objects under the old folder
                ObjectListing objectListing = ossClient.listObjects(bucket, sourcePath);
                List<OSSObjectSummary> objectSummaries = objectListing.getObjectSummaries();

                // Copy each object to the new folder and delete the original
                for (OSSObjectSummary objectSummary : objectSummaries) {
                    String oldObjectKey = objectSummary.getKey();
                    String newObjectKey = oldObjectKey.replaceFirst(sourcePath, targetPath);

                    // Copy the object
                    ossClient.copyObject(bucket, oldObjectKey, bucket, newObjectKey);
                }
            } else {

            }
            CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucket, sourcePath, bucket, targetPath);
            ossClient.copyObject(copyObjectRequest);
        });
    }

    @Override
    public List<DownLoadObjectVO> downloadObjects(String bucket, List<String> objectsPath, Integer expire, String downloadPath) {
        List<DownLoadObjectVO> urls = new ArrayList<>();

        if (objectsPath == null || objectsPath.isEmpty()) {
            return urls;
        }
        List<String> objectKeys = new ArrayList<>();
        // 使用 partitioningBy 根据是否以 '/' 结尾来分组
        Map<Boolean, List<String>> partitioned = objectsPath.stream()
                .collect(Collectors.partitioningBy(s -> s.endsWith("/")));

        objectKeys.addAll(partitioned.get(false));
        List<String> folderList = partitioned.get(true);
        if (folderList != null && !folderList.isEmpty()) {
            for (String folder : folderList) {
                List<String> objects = listAllObjects(bucket, folder);
                objectKeys.addAll(objects);
            }
        }
        // 下载Object到本地文件，并保存到指定的本地路径中。如果指定的本地文件存在会覆盖，不存在则新建。
        // 如果未指定本地路径，则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        if (objectKeys.isEmpty()) {
            return urls;
        }
//        for(String objectKey:objectKeys){
//            ossClient.getObject(new GetObjectRequest(bucket, objectKey), new File(pathName));
//        }
        return signUrls(bucket, objectKeys, expire, downloadPath);
    }


    /**
     * 为多个文件生成下载 URL
     */
    public List<DownLoadObjectVO> signUrls(String bucket, List<String> objectKeys, Integer expirationInMillis, String localPath) {
        List<DownLoadObjectVO> urls = new ArrayList<>();
        if (objectKeys == null || objectKeys.isEmpty()) {
            return urls;
        }
        if (expirationInMillis == null) {
            expirationInMillis = ossClientConfig.getSignExpires();
        }
        String parentFolder = PathUtil.findLongestCommonPath(objectKeys);
        for (String objectKey : objectKeys) {
            DownLoadObjectVO vo = new DownLoadObjectVO();
            if(objectKey.endsWith("/")){
                vo.setIsDir(true);
            }else {
                String downloadUrl = signUrl(bucket, objectKey, expirationInMillis);
                vo.setDownloadUrl(downloadUrl);
                vo.setIsDir(false);
            }
            vo.setLocalPath(Paths.get(localPath, CommonUtils.removeKeyword(objectKey, parentFolder)).normalize().toString());
            urls.add(vo);
        }
        return urls;
    }

    public void downloadAndZipObjects(String bucket, String remoteFolder, ZipOutputStream zos) throws IOException {
        ListObjectsV2Request listObjectsRequest = new ListObjectsV2Request()
                .withBucketName(bucket)
                .withPrefix(remoteFolder)
                .withDelimiter("/");

        ListObjectsV2Result listObjectsResult;
        OSS ossClient = ossClients.get(bucket); // 使用提供的OSS客户端
        do {
            listObjectsResult = ossClient.listObjectsV2(listObjectsRequest);
            List<OSSObjectSummary> objectSummaries = listObjectsResult.getObjectSummaries();

            for (OSSObjectSummary os : objectSummaries) {
                if (!os.getKey().endsWith("/")) { // 跳过文件夹
                    addFileToZip(ossClient, bucket, os.getKey(), zos);
                }
            }

            // 处理子文件夹
            List<String> commonPrefixes = listObjectsResult.getCommonPrefixes();
            for (String prefix : commonPrefixes) {
                if (!prefix.endsWith("/")) {
                    prefix += "/";
                }
                downloadAndZipObjects(bucket, prefix, zos); // 递归处理子文件夹
            }

            listObjectsRequest.setContinuationToken(listObjectsResult.getNextContinuationToken());
        } while (listObjectsResult.isTruncated());
    }

    private void addFileToZip(OSS ossClient, String bucketName, String objectKey, ZipOutputStream zos) throws IOException {
        try (InputStream inputStream = ossClient.getObject(bucketName, objectKey).getObjectContent()) {
            ZipEntry zipEntry = new ZipEntry(objectKey);
            zos.putNextEntry(zipEntry);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
            }
            zos.closeEntry();
        }
    }
}
